/**
 * \file Master.cpp
 *
 * ICC Driver
 * Written 2011, by Anthony Berlin and Alexander Carlqvist
 *
 * This file is part of ICC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * \note \c launch() has not been tested under linux yet.
 */

#include "Master.h"

#include <strutil/strutil.h>
#include <GCB/fileutil.h>

#ifdef __WIN32__
    #include <wincon.h>
#endif

#include <cstring>

///
/// \brief Return the filename of this application.
///
string& Master::get_app_filename()
{
    return APP_FILENAME;
}

///
/// \brief Initialize the application.
///
void Master::init_app(const char* title)
{
    // Application info.
    APP_NAME = "Idioma Compiler Collection";
    APP_ABBR = "ICC";
    APP_VERSION = "0.0.2";
    APP_AUTHOR = "Anthony Berlin & Alex Carlqvist";
    APP_YEAR = "2011";
    APP_TITLE = APP_NAME + " (" + APP_ABBR + ") " + APP_VERSION;

    // Get the name of the executable.
    vector<string> splitVector;

    #ifdef __WIN32__
        if (title[strlen(title) - 4] == '.')
        {
            vector<string> splitVector2;

            split(splitVector, title, boost::is_any_of("."), boost::token_compress_on);
            APP_FILENAME = splitVector[splitVector.size()-2];

            split(splitVector2, APP_FILENAME, boost::is_any_of("\\"), boost::token_compress_on);
            APP_FILENAME = splitVector2[splitVector2.size()-1];
        }
        else
        {
            split(splitVector, title, boost::is_any_of("\\"), boost::token_compress_on);
            APP_FILENAME = splitVector[splitVector.size()-1];
        }
    #endif
    #ifdef __linux__
        split(splitVector, title, boost::is_any_of("/"), boost::token_compress_on);
        APP_FILENAME = splitVector[splitVector.size()-1];
    #endif
    #ifdef __APPLE__
    #endif

    // [Windows]: Set the application title.
    #ifdef __WIN32__
        set_app_title(APP_TITLE);
    #endif

    // Set default param setting.
    p_help = false;
    p_output = "";
    p_verbose = false;
}

///
/// \brief [Windows]: Set the application title.
///
#ifdef __WIN32__
inline void Master::set_app_title(string& title) const
{
    SetConsoleTitle(title.c_str());
}
#endif

///
/// \brief The heart of the program flow.
///
void Master::run(int& argc, char** argv)
{
    GC_INIT();

    // Init basic stuff for the application.
    init_app(argv[0]);

    // Parse the parameters passed to the application.
    parse_params(argc, argv);

    // Exit application if there were any parameter errors.
    if (m_paramErrorList.size())
    {
        foreach (string& paramError, m_paramErrorList)
            cerr << APP_FILENAME << ": " << paramError << "\n";

        throw new Exception("Aborting because of parameter errors.", 4);
    }

    // Print the usage message, if no arguments were passed to the application.
    if (argc == 1)
    {
        usage();
        return;
    }
    // Print the help message, if the '--help' parameter was used.
    else if (p_help)
    {
        print_help();
        return;
    }
    // Print the application version, if the '--version' parameter was used.
    else if (p_version)
    {
        print_version();
        return;
    }

    // If 'm_sourceFiles' is empty, report that we're missing input.
    if (m_sourceFiles.empty())
        throw new Exception("Missing input.", 6);

    // Go through all source files and process them.
    foreach (SourceFile* src, m_sourceFiles)
    {
        // Pass the source file to the appropriate front end.
        output("Compiling: " + src->get_full_path() + "\n");

        // .apl, .am, .as, and files without extensions are assumed to be Asteriskos source files.
        if (src->get_extension() == "apl" ||
            src->get_extension() == "am" ||
            src->get_extension() == "ac" ||
            src->get_extension() == "a" ||
            src->get_extension() == "")
        {
            if (!exec("ipp", src))
                continue;
            if (!exec("ac1", src))
                continue;
            /*
            if (!exec("listc", src))
                continue;
            if (!exec("rtlc", src))
                continue;
            if (!exec("hla", src))
                continue;
            */
        }
    }
}

///
/// \brief Execute a program to process a \c SourceFile with.
/// \param program A \c string containing the program to execute (specify no file extensions).
/// \param src The \c SourceFile to process.
/// \return \c true if the program exited without an error.
///
bool Master::exec(string program, SourceFile* src)
{
    string progid = program;

    // Prepare the argument list.
    list<string> args;

    #ifdef __WIN32__
        program += ".exe";
    #endif

    args.clear();
    args.push_back(program);

    // Setup arguments for the Asteriskos front end.
    if (progid == "ipp")
    {
        return true;;
    }
    else if (progid == "ac1")
    {
        if (!p_verbose)
            args.push_back("-quiet");

        args.push_back(m_sourceFiles.front()->get_full_path());
    }
    else
    {
        cerr << get_app_filename() << ": '" + program + "' is not a supported program.\n";
        return false;
    }

    // Execute the program.
    return !launch(program, args);
}

///
/// \brief Print the help message.
///
inline void Master::print_help() const
{
    usage();

    write_console("Options:");
    write_console("  --help                   Display this information.");
    write_console("  --version                Display the version of the application.");
    write_console("  -o <filename>            Specifiy the output filename.");
    write_console("  -verbose                 Print more information.");
    write_console("");
    write_console("Note: One dash, proceed with execution, two dashes, exit immediately.");
}

///
/// \brief Print the application version.
///
inline void Master::print_version() const
{
    cout << APP_TITLE << ", " << "written by " << APP_AUTHOR << ".\n";
	cout << "Copyright (C) " << APP_YEAR << " Free Software Foundation, Inc.\n";
	cout << "This is free software; see the source for copying conditions. There is NO\n" <<
       "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
}

///
/// \brief Print the usage message.
///
inline void Master::usage() const
{
    cout << "Usage: " << APP_FILENAME << " [options] file\n";
}

///
/// \brief Parse the parameters passed to the application, and configure it.
///
void Master::parse_params(int& argc, char** argv)
{
    int n = 1;
    SourceFile* src = NULL;
    const char* path = 0;
    string msg = "";

    while (n < argc)
    {
        if (argv[n][0] == '-')
        {
            if (!strcmp(argv[n], "-o"))
            {
                if (++n >= argc)
                    missing_arg(--n, argv);

                p_output = argv[n];
            }
            else if (!strcmp(argv[n], "-verbose") || !strcmp(argv[n], "-v"))
                p_verbose = true;
            else if (!strcmp(argv[n], "--help"))
                p_help = true;
            else if (!strcmp(argv[n], "--version") || !strcmp(argv[n], "--ver"))
                p_version = true;
            else
                unrecognized_option(n, argv);
        }
        else
        {
            // Add a source file to the list of source files.
            path = argv[n];
            src = new SourceFile(path);
            m_sourceFiles.push_back(src);

            // Make sure the file exists.
            if (!fileutil::file_exists(src->get_full_path().c_str()))
            {
                msg = (string)"Invalid input '" + src->get_full_path() + "'.";
                push_param_error(msg);
            }
        }

        ++n;
    }
}

///
/// \brief Add a parameter error about in the form of "Unrecognized option 'option'.".
///
inline void Master::unrecognized_option(int& arg, char** argv)
{
    string msg = (string)"Unrecognized option '" + argv[arg] + "'.";
    push_param_error(msg);
}

///
/// \brief Add a parameter error about in the form of "Argument to 'option' is missing.".
///
inline void Master::missing_arg(int& arg, char** argv)
{
    string msg = (string)"Argument to '" + argv[arg] + "' is missing.";
    push_param_error(msg);
}

///
/// \brief Add a parameter error to 'm_paramErrorList'.
///
inline void Master::push_param_error(string& errMsg)
{
    m_paramErrorList.push_back(errMsg);
}

void Master::push_param_error(const char* errMsg)
{
    string msg = errMsg;
    m_paramErrorList.push_back(msg);
}

///
/// \brief Output a string to the screan if the option '-quiet' is not used.
///
template <class T>
void Master::output(T msg) const
{
    if (p_verbose)
        cout << msg;
}

///
/// Write something to the console. It makes sure the string never includes a line that is longer
/// than 79 characters. If it does, an exception is thrown. It always ends with a new-line.
///
void Master::write_console(const char* msg) const
{
    string str = msg;
    write_console(str);
}

void Master::write_console(string& msg) const
{
    vector<string> tokens;

    split(tokens, msg, boost::is_any_of("\n"), boost::token_compress_on);

    foreach (string token, tokens)
    {
        if (token.length() >= 80)
        {
            string exMsg = (string)"Exception: String too long for console window. Length: " +
                boost::lexical_cast<string>(token.length())+". Max: 79.";

            throw new Exception(exMsg.c_str(), 5);
        }
    }

    cout << msg << "\n";
}

///
/// \brief Launch another process.
/// \param path The path to the process.
/// \param args An array of strings containing the arguments to pass the process.
/// \note Some of this code was snatched from the FAQ at \link http://www.cprogramming.com/
///   and modified to fit the needs of this program.
/// \return The exit code of the application.
///
int Master::launch(string path, list<string>& args)
{
    string cmdstr = "";

    // Setup the arguments.
    foreach (string arg, args)
    {
        #ifndef __WIN32__
            if (arg == path)
                continue;
        #endif

        if (cmdstr == "")
            cmdstr = arg;
        else
            cmdstr += (string)" " + arg;
    }

    if (p_verbose)
        output("Executing: \"" + cmdstr + "\"\n");

    #ifdef __WIN32__
        // Gives info on the thread and process for the new process.
        PROCESS_INFORMATION pif;

        // Defines how to start the program.
        STARTUPINFO si;

        // Zero the STARTUPINFO struct.
        ZeroMemory(&si, sizeof(si));

        // Must set size of structure.
        si.cb = sizeof(si);

        // Launch the process.
        BOOL bRet = CreateProcess(
            path.c_str(),   // Path to executable file.
            const_cast<char*>(cmdstr.c_str()), // Command string (arguments).
            NULL,           // Process handle not inherited.
            NULL,           // Thread handle not inherited.
            false,          // No inheritance of handles.
            0,              // No special flags.
            NULL,           // Same environment block as this process.
            NULL,           // Current directory - no separate path.
            &si,            // Pointer to STARTUPINFO.
            &pif);          // Pointer to PROCESS_INFORMATION.

        // See if the process was launched successfully.
        if (!bRet)
            return 1;

        // Wait until the process exits.
        if (WaitForSingleObject(pif.hProcess, INFINITE) == WAIT_FAILED)
            return GetLastError();

        CloseHandle(pif.hProcess);  // Close handle to process.
        CloseHandle(pif.hThread);   // Close handle to thread.

        return 0;
    #endif
    #ifdef __linux__
        // Launch the process.
        system(string(path + " " + cmdstr).c_str());
    #endif
    #ifdef __APPLE__
        // Launch the process.
        system(string(path + " " + cmdstr).c_str());
    #endif
}
